/**
 * Copyright 2010-present Facebook.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.widget;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import com.facebook.Session;
import com.facebook.SessionLoginBehavior;
import com.facebook.SessionState;
import com.facebook.internal.SessionAuthorizationType;
import com.facebook.internal.SessionTracker;

import java.util.Date;
import java.util.List;

/**
 * <p>Basic implementation of a Fragment that uses a Session to perform
 * Single Sign On (SSO). This class is package private, and is not intended
 * to be consumed by external applications.</p>
 * <p/>
 * <p>The method {@link android.support.v4.app.Fragment#onActivityResult} is
 * used to manage the session information, so if you override it in a subclass,
 * be sure to call {@code super.onActivityResult}.</p>
 * <p/>
 * <p>The methods in this class are not thread-safe.</p>
 */
class FacebookFragment extends Fragment
{

  private SessionTracker sessionTracker;

  @Override
  public void onActivityCreated(Bundle savedInstanceState)
  {
    super.onActivityCreated(savedInstanceState);
    sessionTracker = new SessionTracker(getActivity(), new DefaultSessionStatusCallback());
  }

  /**
   * Called when the activity that was launched exits. This method manages session
   * information when a session is opened. If this method is overridden in subclasses,
   * be sure to call {@code super.onActivityResult(...)} first.
   */
  @Override
  public void onActivityResult(int requestCode, int resultCode, Intent data)
  {
    super.onActivityResult(requestCode, resultCode, data);
    sessionTracker.getSession().onActivityResult(this.getActivity(), requestCode, resultCode, data);
  }

  @Override
  public void onDestroy()
  {
    super.onDestroy();
    sessionTracker.stopTracking();
  }

  /**
   * Use the supplied Session object instead of the active Session.
   *
   * @param newSession the Session object to use
   */
  public void setSession(Session newSession)
  {
    if (sessionTracker != null)
    {
      sessionTracker.setSession(newSession);
    }
  }

  // METHOD TO BE OVERRIDDEN

  /**
   * Called when the session state changes. Override this method to take action
   * on session state changes.
   *
   * @param state     the new state
   * @param exception any exceptions that occurred during the state change
   */
  protected void onSessionStateChange(SessionState state, Exception exception)
  {
  }

  // ACCESSORS (CANNOT BE OVERRIDDEN)

  /**
   * Gets the current Session.
   *
   * @return the current Session object.
   */
  protected final Session getSession()
  {
    if (sessionTracker != null)
    {
      return sessionTracker.getSession();
    }
    return null;
  }

  /**
   * Determines whether the current session is open.
   *
   * @return true if the current session is open
   */
  protected final boolean isSessionOpen()
  {
    if (sessionTracker != null)
    {
      return sessionTracker.getOpenSession() != null;
    }
    return false;
  }

  /**
   * Gets the current state of the session or null if no session has been created.
   *
   * @return the current state of the session
   */
  protected final SessionState getSessionState()
  {
    if (sessionTracker != null)
    {
      Session currentSession = sessionTracker.getSession();
      return (currentSession != null) ? currentSession.getState() : null;
    }
    return null;
  }

  /**
   * Gets the access token associated with the current session or null if no
   * session has been created.
   *
   * @return the access token
   */
  protected final String getAccessToken()
  {
    if (sessionTracker != null)
    {
      Session currentSession = sessionTracker.getOpenSession();
      return (currentSession != null) ? currentSession.getAccessToken() : null;
    }
    return null;
  }

  /**
   * Gets the date at which the current session will expire or null if no session
   * has been created.
   *
   * @return the date at which the current session will expire
   */
  protected final Date getExpirationDate()
  {
    if (sessionTracker != null)
    {
      Session currentSession = sessionTracker.getOpenSession();
      return (currentSession != null) ? currentSession.getExpirationDate() : null;
    }
    return null;
  }

  /**
   * Closes the current session.
   */
  protected final void closeSession()
  {
    if (sessionTracker != null)
    {
      Session currentSession = sessionTracker.getOpenSession();
      if (currentSession != null)
      {
        currentSession.close();
      }
    }
  }

  /**
   * Closes the current session as well as clearing the token cache.
   */
  protected final void closeSessionAndClearTokenInformation()
  {
    if (sessionTracker != null)
    {
      Session currentSession = sessionTracker.getOpenSession();
      if (currentSession != null)
      {
        currentSession.closeAndClearTokenInformation();
      }
    }
  }

  /**
   * Gets the permissions associated with the current session or null if no session
   * has been created.
   *
   * @return the permissions associated with the current session
   */
  protected final List<String> getSessionPermissions()
  {
    if (sessionTracker != null)
    {
      Session currentSession = sessionTracker.getSession();
      return (currentSession != null) ? currentSession.getPermissions() : null;
    }
    return null;
  }

  /**
   * Opens a new session. This method will use the application id from
   * the associated meta-data value and an empty list of permissions.
   */
  protected final void openSession()
  {
    openSessionForRead(null, null);
  }

  /**
   * Opens a new session with read permissions. If either applicationID or permissions
   * is null, this method will default to using the values from the associated
   * meta-data value and an empty list respectively.
   *
   * @param applicationId the applicationID, can be null
   * @param permissions   the permissions list, can be null
   */
  protected final void openSessionForRead(String applicationId, List<String> permissions)
  {
    openSessionForRead(applicationId, permissions, SessionLoginBehavior.SSO_WITH_FALLBACK,
        Session.DEFAULT_AUTHORIZE_ACTIVITY_CODE);
  }

  /**
   * Opens a new session with read permissions. If either applicationID or permissions
   * is null, this method will default to using the values from the associated
   * meta-data value and an empty list respectively.
   *
   * @param applicationId the applicationID, can be null
   * @param permissions   the permissions list, can be null
   * @param behavior      the login behavior to use with the session
   * @param activityCode  the activity code to use for the SSO activity
   */
  protected final void openSessionForRead(String applicationId, List<String> permissions,
                                          SessionLoginBehavior behavior, int activityCode)
  {
    openSession(applicationId, permissions, behavior, activityCode, SessionAuthorizationType.READ);
  }

  /**
   * Opens a new session with publish permissions. If either applicationID is null,
   * this method will default to using the value from the associated
   * meta-data value. The permissions list cannot be null.
   *
   * @param applicationId the applicationID, can be null
   * @param permissions   the permissions list, cannot be null
   */
  protected final void openSessionForPublish(String applicationId, List<String> permissions)
  {
    openSessionForPublish(applicationId, permissions, SessionLoginBehavior.SSO_WITH_FALLBACK,
        Session.DEFAULT_AUTHORIZE_ACTIVITY_CODE);
  }

  /**
   * Opens a new session with publish permissions. If either applicationID is null,
   * this method will default to using the value from the associated
   * meta-data value. The permissions list cannot be null.
   *
   * @param applicationId the applicationID, can be null
   * @param permissions   the permissions list, cannot be null
   * @param behavior      the login behavior to use with the session
   * @param activityCode  the activity code to use for the SSO activity
   */
  protected final void openSessionForPublish(String applicationId, List<String> permissions,
                                             SessionLoginBehavior behavior, int activityCode)
  {
    openSession(applicationId, permissions, behavior, activityCode, SessionAuthorizationType.PUBLISH);
  }

  private void openSession(String applicationId, List<String> permissions,
                           SessionLoginBehavior behavior, int activityCode, SessionAuthorizationType authType)
  {
    if (sessionTracker != null)
    {
      Session currentSession = sessionTracker.getSession();
      if (currentSession == null || currentSession.getState().isClosed())
      {
        Session session = new Session.Builder(getActivity()).setApplicationId(applicationId).build();
        Session.setActiveSession(session);
        currentSession = session;
      }
      if (!currentSession.isOpened())
      {
        Session.OpenRequest openRequest = new Session.OpenRequest(this).
            setPermissions(permissions).
            setLoginBehavior(behavior).
            setRequestCode(activityCode);
        if (SessionAuthorizationType.PUBLISH.equals(authType))
        {
          currentSession.openForPublish(openRequest);
        }
        else
        {
          currentSession.openForRead(openRequest);
        }
      }
    }
  }

  /**
   * The default callback implementation for the session.
   */
  private class DefaultSessionStatusCallback implements Session.StatusCallback
  {

    @Override
    public void call(Session session,
                     SessionState state,
                     Exception exception)
    {
      FacebookFragment.this.onSessionStateChange(state, exception);
    }

  }
}
